import threading
import weakref
from decors import valid_kwargs

def locked(lock_name):
    """This is a factory of decorators. The decorator
    wraps an acquire() and release() around the decorated
    method. The lock name is the name of the attribute
    containing the lock."""
    
    if not isinstance(lock_name, basestring):
        raise TypeError("lock name must be a string")
    
    def decorator(func):
        def wrapper(self, *args, **kwargs):
            lock = getattr(self, lock_name)
            lock.acquire()

            # Make sure an exception does not break
            # the lock
            try:
                ret = func(self, *args, **kwargs)
            except:
                lock.release()
                raise
            
            lock.release()
            return ret

        return wrapper

    return decorator

class InnerEvent(object):
    """
        This event is used internally by other objects. It has no 'wait'
        method.
    """
    def __init__(self, or_event):
        self.or_event = weakref.ref(or_event)
        self.lock = threading.Lock()
        self.event = threading.Event()

    def isSet(self):
        return self.event.isSet()

    @locked("lock")
    def set(self):
        self.event.set()
        self.or_event()._trigger()

    @locked("lock")
    def clear(self):
        self.event.clear()
        self.or_event()._trigger()

    def wait(self):
        self.event.wait()
        
class OrEvent(object):
    """
    This event has no 'set' or 'clear' methods, since
    they are called on the wrapped events, through properties
    'event1' and 'event2'.
    """
    def __init__(self):
        self.event1 = InnerEvent(self)
        self.event2 = InnerEvent(self)
        
        self._event = threading.Event()
        self._lock = threading.Lock()

    @locked("_lock")
    def _trigger(self):
        if self.event1.isSet() or self.event2.isSet():
            self._event.set()
        else:
            self._event.clear()

    def isSet(self):
        return self._event.isSet()

    def wait(self):
        self._event.wait()



class Sleeper(object):
    def __init__(self):
        self.evt = threading.Event()
        self.evt.clear()

    def __call__(self, interval):
        self.evt.wait(interval)

sleep = Sleeper()


def _validate_index(index, none_valid=True):
    if none_valid:
        valid_args = (0, -1, None)
    else:
        valid_args = (0, -1)

    if index not in valid_args:
        raise TypeError("Accepted indexes are: %s, not %r" % (
            ", ".join(map(str, valid_args)),
            index))



class BlockingList(object):
    """
    A BlockingList object unites a semaphore with a list.
    This makes poping elements from the list block when there are
    not enough elements. The consumer/producer problem.
    """
    @valid_kwargs(is_empty=threading.Event, is_not_empty=threading.Event)
    def __init__(self, *args, **kwargs):
        self.sem = threading.Semaphore(len(args))
        self.lock = threading.Lock()
        self.values = list(args)
        # 'is_set' and 'is_not_set' attributes
        self.__dict__.update(kwargs)
        if len(args) == 0:
            self.is_empty.set()
        else:
            self.is_not_empty.set()

    @locked("lock")
    def append(self, val):
        """Append is called by the producer"""
        self.values.append(val)
        self.sem.release()
        self.is_empty.clear()
        self.is_not_empty.set()

    def pop(self, *args):
        """Pop is called by the consumer"""
        if len(args) > 1:
            raise TypeError("Accepts 1 optional argument.")
        if len(args) == 1:
            _validate_index(args[0])
        
        self.sem.acquire()
        self.lock.acquire()
        val = self.values.pop(*args)
        if len(self.values) == 0:
            self.is_empty.set()
            self.is_not_empty.clear()
        self.lock.release()
        return val

    def remove(self, value):
        """Remove an object from the list"""
        # make sure we have one element
        self.sem.acquire()
        # lock 'values'
        self.lock.acquire()
        try:
            self.values.remove(value)
            if len(self.values) == 0:
                self.is_empty.set()
                self.is_not_empty.clear()
        except ValueError:
            # the value is not there, make sure
            # the semaphore is back up and raise
            # the error again
            self.sem.release()
            # unlock 'values' before raising
            self.lock.release()
            raise
        
        self.lock.release()

    def __iter__(self):
        self.is_not_empty.wait()
        return iter(self.values)
    
    @locked("lock")
    def __len__(self):
        return len(self.values)

    @locked("lock")
    def insert(self, index, object):
        _validate_index(index)
        self.values.insert(index, object)
        self.sem.release()
        self.is_empty.clear()
        self.is_not_empty.set()


